package org.iabako.server.serviceimpl.report;

import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import org.iabako.server.dao.business.SaleDAO;
import org.iabako.server.dao.report.IncomeReportDAO;
import org.iabako.server.serviceimpl.security.SessionUser;
import org.iabako.server.tools.BundleMessages;
import org.iabako.server.tools.IabakoWorkbook;
import org.iabako.server.tools.ServerTools;
import org.iabako.shared.dto.FinancesBalance;
import org.iabako.shared.dto.ReportByEnum;
import org.iabako.shared.dto.ReportGenerationInputDTO;
import org.iabako.shared.entity.business.Client;
import org.iabako.shared.entity.business.Payment;
import org.iabako.shared.entity.business.Sale;
import org.iabako.shared.entity.enumeration.TrackingType;
import org.iabako.shared.iservice.business.TrackingService;
import org.iabako.shared.tools.GenericTools;
import org.springframework.beans.factory.annotation.Autowired;

import java.util.*;

/**
 * Created by jose on 27/01/15.
 */

public abstract class AbstractReportService {

    @Autowired
    IncomeReportDAO incomeReportDAO;
    @Autowired
    SaleDAO saleDAO;
    @Autowired
    BundleMessages messages;

    @Autowired
    SessionUser userComponent;
    @Autowired
    TrackingService trackingService;

    protected String generateReport(ReportGenerationInputDTO reportGenerationInputDTO) {

        ServerTools serverTools = new ServerTools();
        Date dateAfter = GenericTools.getDateFromString(reportGenerationInputDTO.getPeriod1DateAfter());
        Date dateBefore = GenericTools.getDateFromString(reportGenerationInputDTO.getPeriod1DateBefore());

        List<String> listHeader = new ArrayList<String>();
        List<Map<Object, FinancesBalance>> listData = new ArrayList<Map<Object, FinancesBalance>>();

        Date dateIterationStart = dateAfter;
        Date dateIterationEnd;
        Date nextDateIteration;
        Map<Object, FinancesBalance> dataByEntityMap;

        if (dateAfter.getMonth() == dateBefore.getMonth()) {
            //Same month, get month information

            listHeader.add(GenericTools.formatDate(dateAfter) + " - " + GenericTools.formatDate(dateBefore));
            dataByEntityMap = getDataMapByEntity(reportGenerationInputDTO, dateAfter, serverTools.addDaysToDate(dateBefore, 1));
            listData.add(dataByEntityMap);

        } else if (dateAfter.getYear() == dateBefore.getYear()) {
            //Same year, get monthly information

            while (dateIterationStart.compareTo(dateBefore) <= 0) {

                dateIterationEnd = serverTools.addMonthsToDate(dateIterationStart, 1);
                dateIterationEnd.setDate(1);
                dateIterationEnd = serverTools.addDaysToDate(dateIterationEnd, -1);

                if (dateIterationEnd.compareTo(dateBefore) > 0) {
                    dateIterationEnd = dateBefore;
                }
                nextDateIteration = serverTools.addDaysToDate(dateIterationEnd, 1);

                listHeader.add(GenericTools.formatDate(dateIterationStart) + " - " + GenericTools.formatDate(dateIterationEnd));
                dataByEntityMap = getDataMapByEntity(reportGenerationInputDTO, dateIterationStart, nextDateIteration);
                listData.add(dataByEntityMap);

                dateIterationStart = nextDateIteration;
            }
        } else {
            //get yearly information
            while (dateIterationStart.compareTo(dateBefore) <= 0) {

                dateIterationEnd = serverTools.addYearsToDate(dateIterationStart, 1);
                dateIterationEnd.setDate(1);
                dateIterationEnd.setMonth(0);
                dateIterationEnd = serverTools.addDaysToDate(dateIterationEnd, -1);

                if (dateIterationEnd.compareTo(dateBefore) > 0) {
                    dateIterationEnd = dateBefore;
                }
                nextDateIteration = serverTools.addDaysToDate(dateIterationEnd, 1);

                String dateEnd = GenericTools.formatDate(dateIterationEnd);
                if (dateIterationEnd.compareTo(dateBefore) > 0) {
                    dateEnd = GenericTools.formatDate(dateBefore);
                }
                listHeader.add(GenericTools.formatDate(dateIterationStart) + " - " + dateEnd);
                dataByEntityMap = getDataMapByEntity(reportGenerationInputDTO, dateIterationStart, nextDateIteration);
                listData.add(dataByEntityMap);

                dateIterationStart = nextDateIteration;
            }
        }

        //totalHeader
        if (reportGenerationInputDTO.getReportByEnum() != ReportByEnum.byInvoice) {
            listHeader.add(GenericTools.formatDate(dateAfter) + " - " + GenericTools.formatDate(dateBefore));
        }

        return generateReport(reportGenerationInputDTO, listHeader, listData);
    }

    protected abstract Map<Object, FinancesBalance> getDataMapByEntity(ReportGenerationInputDTO reportGenerationInputDTO, Date dateAfter, Date dateBefore);

    protected abstract String getReportFileName();

    protected abstract String getReportTitle();

    protected abstract String getColumnStyleLabel1();

    protected abstract String getColumnStyleLabel2();

    protected abstract Double getColumnValue1(FinancesBalance financesBalance);

    protected abstract Double getColumnValue2(FinancesBalance financesBalance);

    protected Map<Object, FinancesBalance[]> getMapByEntityFromListData(List<Map<Object, FinancesBalance>> listData) {

        Map<Object, FinancesBalance[]> listDataByEntity = new HashMap<Object, FinancesBalance[]>();

        int indexData = 0;
        for (Map<Object, FinancesBalance> data : listData) {
            for (Object entity : data.keySet()) {
                if (listDataByEntity.get(entity) == null) {
                    listDataByEntity.put(entity, new FinancesBalance[listData.size()]);
                }
                FinancesBalance[] financesDataList = listDataByEntity.get(entity);
                financesDataList[indexData] = data.get(entity);
            }
            indexData++;
        }

        return listDataByEntity;
    }

    protected void cleanZeros(Map<Object, FinancesBalance[]> listDataByEntity) {
        List<Object> entityToDelete = new ArrayList<Object>();
        for (Object entity : listDataByEntity.keySet()) {
            boolean atLeastOneValue = false;
            for (FinancesBalance financesBalance : listDataByEntity.get(entity)) {
                if (financesBalance != null &&
                        (financesBalance.getSalesReceivable() != 0
                                || financesBalance.getSalesPayed() != 0
                                || financesBalance.getExpensesPayable() != 0
                                || financesBalance.getExpensesPayed() != 0)) {
                    atLeastOneValue = true;
                    break;
                }
            }
            if (!atLeastOneValue) {
                entityToDelete.add(entity);
            }
        }

        for (Object entity : entityToDelete) {
            listDataByEntity.remove(entity);
        }
    }

    protected int getFreezePanelSize(ReportGenerationInputDTO reportGenerationInputDTO) {
        switch (reportGenerationInputDTO.getReportByEnum()) {
            case byClient:
                return 2;
            case bySale:
                return 3;
            case byInvoice:
                return 1;
            case byExpenseCategory:
                return 1;
            case byProfit:
                return 1;
        }
        return 0;
    }

    protected String getEntityLabel(ReportGenerationInputDTO reportGenerationInputDTO) {
        switch (reportGenerationInputDTO.getReportByEnum()) {
            case byClient:
                return messages.getLabel("client");
            case bySale:
                return messages.getLabel("sale");
            case byInvoice:
                return messages.getLabel("invoice");
            case byExpenseCategory:
                return messages.getLabel("expense");
            case byProfit:
                return "";
        }
        return null;
    }

    protected int getColumnOffset(ReportGenerationInputDTO reportGenerationInputDTO) {
        switch (reportGenerationInputDTO.getReportByEnum()) {
            case byClient:
                return 5;
            case bySale:
                return 6;
            case byInvoice:
                return 9;
            case byExpenseCategory:
                return 1;
            case byProfit:
                return 1;
        }
        return 0;
    }

    protected void addEntityHeader(ReportGenerationInputDTO reportGenerationInputDTO, IabakoWorkbook iabakoWorkbook) {
        switch (reportGenerationInputDTO.getReportByEnum()) {
            case byClient:
                iabakoWorkbook.addHeaderDefinition(2000, messages.getLabel("result_table_client_number"));
                iabakoWorkbook.addHeaderDefinition(5000, messages.getLabel("result_table_client_name"));
                iabakoWorkbook.addHeaderDefinition(4000, messages.getLabel("result_table_client_email"));
                iabakoWorkbook.addHeaderDefinition(5000, messages.getLabel("result_table_client_addressLine1"));
                iabakoWorkbook.addHeaderDefinition(2500, messages.getLabel("result_table_client_phoneNumber1"));
                break;
            case bySale:
                iabakoWorkbook.addHeaderDefinition(2000, messages.getLabel("result_table_sale_number"));
                iabakoWorkbook.addHeaderDefinition(3000, messages.getLabel("result_table_sale_date"));
                iabakoWorkbook.addHeaderDefinition(3000, messages.getLabel("result_table_sale_price"));
                iabakoWorkbook.addHeaderDefinition(2000, messages.getLabel("result_table_sale_client_number"));
                iabakoWorkbook.addHeaderDefinition(5000, messages.getLabel("result_table_sale_client"));
                iabakoWorkbook.addHeaderDefinition(5000, messages.getLabel("result_table_sale_product_service"));
                break;
            case byInvoice:
                iabakoWorkbook.addHeaderDefinition(2000, messages.getLabel("report_income_by_invoice_number"));
                iabakoWorkbook.addHeaderDefinition(2000, messages.getLabel("report_income_by_invoice_sale_number"));
                iabakoWorkbook.addHeaderDefinition(3000, messages.getLabel("report_income_by_invoice_sale_date"));
                iabakoWorkbook.addHeaderDefinition(3000, messages.getLabel("report_income_by_invoice_sale_price"));
                iabakoWorkbook.addHeaderDefinition(2000, messages.getLabel("result_table_sale_client_number"));
                iabakoWorkbook.addHeaderDefinition(5000, messages.getLabel("result_table_sale_client"));
                iabakoWorkbook.addHeaderDefinition(5000, messages.getLabel("result_table_sale_product_service"));
                iabakoWorkbook.addHeaderDefinition(3000, messages.getLabel("report_income_by_invoice_scheduled_date"));
                iabakoWorkbook.addHeaderDefinition(3000, messages.getLabel("report_income_by_invoice_date"));
                break;
            case byExpenseCategory:
                iabakoWorkbook.addHeaderDefinition(5000, messages.getLabel("result_table_expense_category"));
                break;
            case byProfit:
                iabakoWorkbook.addHeaderDefinition(4000, "");
                break;
        }
    }

    protected void addEntityInfo(IabakoWorkbook iabakoWorkbook, Row row, int indexColumn, CellStyle cellStyle, Object entity) {
        if (entity instanceof Client) {
            Client client = (Client) entity;
            iabakoWorkbook.fillCell(row.createCell(indexColumn++), cellStyle, client.getNumber());
            iabakoWorkbook.fillCell(row.createCell(indexColumn++), cellStyle, client.getName());
            iabakoWorkbook.fillCell(row.createCell(indexColumn++), cellStyle, client.getEmail());
            iabakoWorkbook.fillCell(row.createCell(indexColumn++), cellStyle, client.getAddress() != null ? client.getAddress().getAddressLine1() : null);
            iabakoWorkbook.fillCell(row.createCell(indexColumn), cellStyle, client.getPhoneNumber1());

        } else if (entity instanceof Sale) {
            Sale sale = (Sale) entity;
            iabakoWorkbook.fillCell(row.createCell(indexColumn++), cellStyle, sale.getNumber());
            iabakoWorkbook.fillCell(row.createCell(indexColumn++), iabakoWorkbook.getStyles().get("cellDateSmall"), sale.getDate());
            iabakoWorkbook.fillCell(row.createCell(indexColumn++), iabakoWorkbook.getStyles().get("cellAmountSmall"), incomeReportDAO.getPriceForSale(sale));
            iabakoWorkbook.fillCell(row.createCell(indexColumn++), cellStyle, sale.getClient() != null ? sale.getClient().getNumber() : "-");
            iabakoWorkbook.fillCell(row.createCell(indexColumn++), cellStyle, sale.getClient() != null ? sale.getClient().getName() : "-");
            iabakoWorkbook.fillCell(row.createCell(indexColumn), cellStyle, sale.getProductsServicesAsString());

        } else if (entity instanceof Payment) {
            Payment payment = (Payment) entity;
            Sale sale = payment.getSale();
            List<Payment> paymentsList = saleDAO.getPayments(sale);
            int paymentIndex = paymentsList.indexOf(payment) + 1;
            iabakoWorkbook.fillCell(row.createCell(indexColumn++), cellStyle, sale.getNumber() + "-" + paymentIndex);
            iabakoWorkbook.fillCell(row.createCell(indexColumn++), cellStyle, payment.getSale().getNumber());
            iabakoWorkbook.fillCell(row.createCell(indexColumn++), iabakoWorkbook.getStyles().get("cellDateSmall"), sale.getDate());
            iabakoWorkbook.fillCell(row.createCell(indexColumn++), iabakoWorkbook.getStyles().get("cellAmountSmall"), incomeReportDAO.getPriceForSale(sale));
            iabakoWorkbook.fillCell(row.createCell(indexColumn++), cellStyle, sale.getClient() != null ? sale.getClient().getNumber() : "-");
            iabakoWorkbook.fillCell(row.createCell(indexColumn++), cellStyle, sale.getClient() != null ? sale.getClient().getName() : "-");
            iabakoWorkbook.fillCell(row.createCell(indexColumn++), cellStyle, sale.getProductsServicesAsString());
            iabakoWorkbook.fillCell(row.createCell(indexColumn++), iabakoWorkbook.getStyles().get("cellDateSmall"), payment.getScheduleDate() != null ? payment.getScheduleDate() : "-");
            iabakoWorkbook.fillCell(row.createCell(indexColumn), iabakoWorkbook.getStyles().get("cellDateSmall"), payment.getDate());

        } else {
            iabakoWorkbook.fillCell(row.createCell(indexColumn), cellStyle, entity);
        }
    }

    private String generateReport(ReportGenerationInputDTO reportGenerationInputDTO, List<String> listHeader, List<Map<Object, FinancesBalance>> listData) {

        Map<Object, FinancesBalance[]> listDataByEntity = getMapByEntityFromListData(listData);

        int columnOffset = getColumnOffset(reportGenerationInputDTO);

        IabakoWorkbook iabakoWorkbook = new IabakoWorkbook(getReportFileName(),
                getReportFileName(), userComponent.getUser().getEnterprise().getName());

        addEntityHeader(reportGenerationInputDTO, iabakoWorkbook);

        int indexColumn = 0;
        for (String header : listHeader) {
            iabakoWorkbook.addHeaderDefinition(3000, header);
            if (reportGenerationInputDTO.getReportByEnum() != ReportByEnum.byInvoice) {
                //Added twice and for later concatenation
                iabakoWorkbook.addHeaderDefinition(3000, header);
            }
        }
        int firstRow;
        int columnsByData;
        int lastColumnWithoutTotal;
        switch (reportGenerationInputDTO.getReportByEnum()) {
            case byInvoice:
                firstRow = 0;
                columnsByData = 1;
                lastColumnWithoutTotal = columnOffset + (listHeader.size() * columnsByData) - 1;
                break;
            default:
                firstRow = 3;
                columnsByData = 2;
                lastColumnWithoutTotal = columnOffset + ((listHeader.size() - 1) * columnsByData) - 1;
        }

        Sheet sheet = iabakoWorkbook.getSheets().get(0);
        iabakoWorkbook.addHeader(sheet, true, firstRow + 1);
        sheet.createFreezePane(getFreezePanelSize(reportGenerationInputDTO), firstRow + 2);
        sheet.setRepeatingRows(new CellRangeAddress(0, firstRow + 1, 0, columnOffset + (listHeader.size() * columnsByData) - 1));

        CellStyle style1 = iabakoWorkbook.getStyles().get("cellAmountSmallYellow");
        CellStyle style2 = iabakoWorkbook.getStyles().get("cellAmountSmallBlue");
        Cell headerCell;
        if (reportGenerationInputDTO.getReportByEnum() != ReportByEnum.byInvoice) {
            headerCell = sheet.createRow(0).createCell(0);
            headerCell.setCellStyle(style1);

            headerCell = sheet.createRow(1).createCell(0);
            headerCell.setCellStyle(style2);
            if (columnOffset > 1) {
                headerCell = sheet.getRow(0).createCell(1);
                headerCell.setCellValue(getColumnStyleLabel1());
                headerCell = sheet.getRow(1).createCell(1);
                headerCell.setCellValue(getColumnStyleLabel2());
            } else {
                headerCell = sheet.getRow(0).getCell(0);
                headerCell.setCellValue(getColumnStyleLabel1());
                headerCell = sheet.getRow(1).getCell(0);
                headerCell.setCellValue(getColumnStyleLabel2());
            }
        }

        sheet.addMergedRegion(new CellRangeAddress(firstRow, firstRow, 0, columnOffset - 1));

        headerCell = sheet.createRow(firstRow).createCell(0);
        headerCell.setCellStyle(iabakoWorkbook.getStyles().get("headerDarker"));
        headerCell.setCellValue(getEntityLabel(reportGenerationInputDTO));

        sheet.getRow(firstRow).setHeightInPoints(20);

        //Range for income label (without last total column)
        sheet.addMergedRegion(new CellRangeAddress(firstRow, firstRow, columnOffset, lastColumnWithoutTotal));

        headerCell = sheet.getRow(firstRow).createCell(columnOffset);
        headerCell.setCellValue(getReportTitle());

        //Total super header
        if (reportGenerationInputDTO.getReportByEnum() != ReportByEnum.byInvoice) {
            sheet.addMergedRegion(new CellRangeAddress(firstRow, firstRow, lastColumnWithoutTotal + 1, lastColumnWithoutTotal + columnsByData));
            headerCell = sheet.getRow(firstRow).createCell(columnOffset + listHeader.size() * columnsByData - columnsByData);
            headerCell.setCellValue(messages.getLabel("report_total"));
        }

        for (int i = columnOffset; i < columnOffset + (listHeader.size() * columnsByData); i++) {
            headerCell = sheet.getRow(firstRow).getCell(i);
            if (headerCell == null) {
                headerCell = sheet.getRow(firstRow).createCell(i);
            }
            headerCell.setCellStyle(iabakoWorkbook.getStyles().get("headerDarker"));

            if ((i - columnOffset) % columnsByData == 0) {
                sheet.addMergedRegion(new CellRangeAddress(firstRow + 1, firstRow + 1, i, i + columnsByData - 1));
            }
        }

        //clean zeros
        cleanZeros(listDataByEntity);

        double[] verticalSum = new double[listHeader.size() * columnsByData];

        int indexRow = firstRow + 2;
        for (Object entity : listDataByEntity.keySet()) {
            indexColumn = 0;
            Row row = sheet.createRow(indexRow++);
            row.setHeightInPoints(25);

            CellStyle cellStyle = iabakoWorkbook.getStyles().get("cellSmall");

            addEntityInfo(iabakoWorkbook, row, indexColumn, cellStyle, entity);

            indexColumn += columnOffset;

            //data
            Double value1Sum = 0D;
            Double value2Sum = 0D;
            for (FinancesBalance financesBalance : listDataByEntity.get(entity)) {
                Double value1 = 0D;
                Double value2 = 0D;
                if (financesBalance != null) {
                    value1 = getColumnValue1(financesBalance);
                    value2 = getColumnValue2(financesBalance);
                }
                if (reportGenerationInputDTO.getReportByEnum() != ReportByEnum.byInvoice) {
                    verticalSum[indexColumn - columnOffset] += getVerticalSumNumber(reportGenerationInputDTO, entity, value1);
                    iabakoWorkbook.fillCell(row.createCell(indexColumn++), style1, value1 == 0 ? "-" : value1);
                }
                verticalSum[indexColumn - columnOffset] += getVerticalSumNumber(reportGenerationInputDTO, entity, value2);
                iabakoWorkbook.fillCell(row.createCell(indexColumn++), style2, value2 == 0 ? "-" : value2);

                value1Sum += value1;
                value2Sum += value2;
            }

            //Horizontal total
            if (reportGenerationInputDTO.getReportByEnum() != ReportByEnum.byInvoice) {

                verticalSum[indexColumn - columnOffset] += getVerticalSumNumber(reportGenerationInputDTO, entity, value1Sum);
                iabakoWorkbook.fillCell(row.createCell(indexColumn++), style1, value1Sum == 0 ? "-" : value1Sum);
                verticalSum[indexColumn - columnOffset] += getVerticalSumNumber(reportGenerationInputDTO, entity, value2Sum);
                iabakoWorkbook.fillCell(row.createCell(indexColumn++), style2, value2Sum == 0 ? "-" : value2Sum);
            }
        }

        int verticalTotalRowIndex = firstRow + 2 + listDataByEntity.keySet().size();
        Row verticalTotalRow = sheet.createRow(verticalTotalRowIndex);
        sheet.getRow(verticalTotalRowIndex).setHeightInPoints(20);

        sheet.addMergedRegion(new CellRangeAddress(verticalTotalRowIndex, verticalTotalRowIndex, 0, columnOffset - 1));

        Cell verticalTotalCellLabel = verticalTotalRow.createCell(0);
        verticalTotalCellLabel.setCellStyle(iabakoWorkbook.getStyles().get("headerDarker"));
        verticalTotalCellLabel.setCellValue(getVerticalTotalLabel());

        for (int i = columnOffset; i < columnOffset + (listHeader.size() * columnsByData); i++) {
            if (reportGenerationInputDTO.getReportByEnum() != ReportByEnum.byInvoice) {
                iabakoWorkbook.fillCell(verticalTotalRow.createCell(i), style1, verticalSum[i - columnOffset]);
                i++;
            }
            iabakoWorkbook.fillCell(verticalTotalRow.createCell(i), style2, verticalSum[i - columnOffset]);
        }

        sheet.setRepeatingColumns(new CellRangeAddress(0, verticalTotalRowIndex, 0, getFreezePanelSize(reportGenerationInputDTO) - 1));

        trackingService.addTrackingToUserSession(TrackingType.reportGenerate, null, 1, messages.getLabel("tracking_report_generate_comment", getReportFileName(), GenericTools.formatTime(new Date())));

        return iabakoWorkbook.addFileToSession();
    }

    private double getVerticalSumNumber(ReportGenerationInputDTO reportGenerationInputDTO, Object entity, Double value) {
        if (reportGenerationInputDTO.getReportByEnum() == ReportByEnum.byProfit) {
            if (entity.equals(messages.getLabel("sales"))) {
                return value;
            } else {
                return value * -1;
            }
        } else {
            return value;
        }
    }

    protected String getVerticalTotalLabel() {
        return messages.getLabel("report_total");
    }
}
